/*
 *          Terrain.glsl
 *
 *      This is a 'GLSL 4.10 core profile' code.
 */

//------------------------------------------------------------------------------

-- Vertex


// IN
layout(location = 0) in vec2 inPosition;

// OUT
out vec2 vPosition;


void main()
{
  vPosition = inPosition;
}


--

//------------------------------------------------------------------------------


-- TessControl


// PARAMETER
layout(vertices = 1) out;

// IN
in vec2 vPosition[];

// OUT
out vec2 tcPosition[];
out vec2 tcSize[];

// UNIFORM
uniform mat4  uModelViewProjMatrix;
uniform vec3  uEyePos;
uniform vec3  uEyeDir;

uniform float uStaticTessFactor = 12.0f;//
uniform float uDynamicTessFactor = 50.0f;//
uniform bool  uFrustumCullInTC = false;//
uniform bool  uUseDynamicLOD = false;

uniform sampler2D uTerrainTex;              // RGB : normal, A : height
uniform float     uTerrainScaledResolution;
uniform float     uPatchSize;

// PROTOTYPE
bool insideFrustum();
float computeTessFactor(float d);


#define ID  gl_InvocationID

void main()
{
  tcPosition[ID] = vPosition[ID];


  // Il n'y en a qu'un normalement.. mais faisons comme ça pour le moment.
  if (ID == 0)
  {    
    bool bInFrustum = insideFrustum();
    
    if (bInFrustum || uFrustumCullInTC)
    {
      float d = 0.0f;
      
      // Here PatchSize is the same in both direction and for the whole mesh,
      // thus the use of a uniform variable. If it changes it can be put in a 
      // vertex buffer.
      vec2 left   = vPosition[0] + uPatchSize*vec2( 0.0f, 0.5f);
      vec2 bottom = vPosition[0] + uPatchSize*vec2( 0.5f, 0.0f);
      vec2 right  = vPosition[0] + uPatchSize*vec2( 1.0f, 0.5f);
      vec2 top    = vPosition[0] + uPatchSize*vec2( 0.5f, 1.0f);
      
      d = length(uEyePos.xz - left);
      gl_TessLevelOuter[0] = computeTessFactor( d );
      
      d = length(uEyePos.xz - bottom);
      gl_TessLevelOuter[1] = computeTessFactor( d );
      
      d = length(uEyePos.xz - right);
      gl_TessLevelOuter[2] = computeTessFactor( d );
      
      d = length(uEyePos.xz - top);
      gl_TessLevelOuter[3] = computeTessFactor( d );
      
      
      // Inner levels are set as the average of the Outer levels
      float avgOuter = 0.25f* ( gl_TessLevelOuter[0] + gl_TessLevelOuter[1] + 
                                gl_TessLevelOuter[2] + gl_TessLevelOuter[3] );
      
      gl_TessLevelInner[0] = gl_TessLevelInner[1] = avgOuter;
    }
    else
    {

      gl_TessLevelOuter[0] = -1.0f;
      gl_TessLevelOuter[1] = -1.0f;
      gl_TessLevelOuter[2] = -1.0f;
      gl_TessLevelOuter[3] = -1.0f;
      
      gl_TessLevelInner[0] = -1.0f;
      gl_TessLevelInner[1] = -1.0f;
    }
  }
}


bool insideFrustum()
{  
  vec3 patchCenter;
  vec2 texCoord;
 
  // Offset to the patch center
  vec2 offset = uPatchSize * vec2(0.5f);
  
  // Patch center's 2D coordinate
  patchCenter.xz = vPosition[0] + offset;

  // Patch center's texCoord (in range [0:1])
  texCoord = patchCenter.xz / uTerrainScaledResolution;
  //texCoord.t = 1.0f - texCoord.t;

  // Height of the patch center
  patchCenter.y = texture( uTerrainTex, texCoord).a;
  

  //-----------
  // OK, deep stuff here
  // Simply put, it will create the smallest vector beetween the Patch-center and
  // the Eye direction vector.
  // So grab a pen and a paper, and learn your math !
  
  // hey ! simple vector equation (OA = A - O) :
  vec3 eyeToPatch = patchCenter - uEyePos;  
  
  // Between all the tremendous things a Dot Product can make, here is one :
  // Let u & v two vectors, dot(u, v) = |u|*|v|*cos(uv)
  // and |v|*cos(uv) is the scalar to make the projeté of 'u' on 'v'.
  // Here we have uEyeDir, a normalized vector (length=1).
  vec3 eyeDir = normalize(uEyeDir); // to be sure !!
  float dp = dot( eyeToPatch, eyeDir);

  // By multiplying uEyeDir by 'dp', we've then got the projeté of eyeToPatch to the eye direction.
  // (eyeToPatch is also the patch coordinate in eye space)
  vec3 scaleEyeDir = dp*eyeDir;  
  // Finally, we create the vector from the patch to the eyeDir vector (eye space)
  vec3 patchToEyeDir = scaleEyeDir - eyeToPatch;
  
  // It seems to virtually 'bring' the patch closer to the eyeDir vector (but why 2.0f*uPatchSize.x ?)
  float maxDist = 2.0f*uPatchSize;
  patchToEyeDir = normalize(patchToEyeDir) * min( maxDist, length(patchToEyeDir));  
  vec3 patchCenterRealigned = patchCenter + patchToEyeDir;
  // Screen-space patch center
  vec4 patchCenter_sp = uModelViewProjMatrix *  vec4(patchCenterRealigned, 1.0f);
  patchCenter_sp.xyz /= patchCenter_sp.w;
  
  // inside frustum (screen-space)
  bool b1 = (patchCenter_sp.x > -1.0f) && (patchCenter_sp.x < 1.0f) &&  
            (patchCenter_sp.y > -1.0f) && (patchCenter_sp.y < 1.0f) &&
            (patchCenter_sp.w >  0.0f);
  // ?? dist to the view vector ?
  bool b2 = (length(eyeToPatch) < maxDist);  
  
  bool bInFrustum = b1 || b2;  
  // -----------  
  
  return bInFrustum;
}

// Switch between static and dynamic factor.
// (uUseDynamicLOD is either 0 or 1).
float computeTessFactor(float d)
{
  float coef = 20.0f / d;
  return mix( uStaticTessFactor, coef*uDynamicTessFactor, uUseDynamicLOD);
}


--


//------------------------------------------------------------------------------


-- TessEval


// PARAM
layout(quads) in;
layout(fractional_odd_spacing) in;
layout(cw) in;

// IN
in vec2 tcPosition[];

// OUT
out vec3 tePosition;
out vec3 teNormal;
out vec2 teTexCoord;

// UNIFORMS
uniform float     uPatchSize;
uniform float     uTerrainScaledResolution;
uniform sampler2D uTerrainTex;
uniform mat4      uModelViewProjMatrix;
// For caustics simulation
uniform bool      uRenderCaustics;
uniform vec3      uLightPosition;       // more like 'uSunDirection'
uniform vec3      uEyePos;
uniform sampler2D uWaterNormalmapTex;
//For.. something XXX
uniform float uStaticTessFactor;
uniform float uDynamicTessFactor;
uniform bool  uUseDynamicLOD;
//..XXX
uniform sampler2D uNormalmap0Tex;     // sand
uniform sampler2D uNormalmap1Tex;     // rock
uniform sampler2D uLayerDefTexture;
uniform float     uSandBumpUVScale = 0.0f;
uniform float     uSandBumpHeightScale = 0.0f;
uniform float     uRockBumpUVScale  = 0.0f;
uniform float     uRockBumpHeightScale = 0.0f;

// PROTOTYPE
float computeTessFactor(float d);
float computeWaterCaustics(vec3 worldpos);



void main()
{
  // TODO : simplify the rendering for reflection
  //        (eg. by using subroutines).

  vec3 position;
  vec3 normal;
  vec2 texCoord;
  
  // Location on the quad patch
  vec2 uv = gl_TessCoord.xy;

  // 2D Position
  position.xz = tcPosition[0] + uPatchSize * uv;
  
  // TexCoord
  texCoord = position.xz / uTerrainScaledResolution;
  //texCoord.t = 1.0f - texCoord.t;//
  
  // Retrieve Normal(RGB) & Height(A)
  vec4 heightmap = textureLod( uTerrainTex, texCoord, 0);  

  // Note : normals can also be compute in a shader
  normal = heightmap.rgb;
  //normal.z = -normal.z;
    
  // Height of the vertice
  position.y = heightmap.a;
  
  //XXX pas suivis.. du tout
    float distToEye = distance( position, uEyePos );
    float detailmap_level = log2(128.f/computeTessFactor(distToEye));

    vec4 layerdef = textureLod( uLayerDefTexture, texCoord,0);//XXX
    
    vec4 detail_texvalue  = textureLod( uNormalmap0Tex, uSandBumpUVScale * texCoord, detailmap_level);
    vec3 detail_normal    = normalize( 2*detail_texvalue.xyz - vec3(1.f, 0.f, 1.f));
    float detail_height   = (detail_texvalue.w - 0.5f) * uSandBumpHeightScale;
    
    detail_texvalue   = textureLod( uNormalmap1Tex, uRockBumpUVScale * texCoord, detailmap_level);
    vec3 rock_dnormal = normalize(2.f*detail_texvalue.xyz - vec3(1.f,1.4f,1.f));
    detail_normal     = mix( detail_normal, rock_dnormal, layerdef.w);//XXX
    float rock_dheight = (detail_texvalue.w - 0.5f) * uRockBumpHeightScale; //XXX
    detail_height = mix( detail_height, rock_dheight, layerdef.w);
    

    // moving vertices by detail height along base normal
    position += normal*detail_height;
/*
    //calculating base normal rotation matrix
    normal_rotation_matrix[1] = base_normal;
    normal_rotation_matrix[2] = normalize(cross(float3(-1.0,0.0,0.0),normal_rotation_matrix[1]));
    normal_rotation_matrix[0] = normalize(cross(normal_rotation_matrix[2],normal_rotation_matrix[1]));

    //applying base rotation matrix to detail normal
    detail_normal_rotated   = mul(detail_normal, normal_rotation_matrix);    
*/  
  
  float caustics = 0.0f;
  if (uRenderCaustics) {
    caustics = computeWaterCaustics( position.xyz );
  }

  // XXX This can change if I add the rendering techniques XXX
  gl_Position = uModelViewProjMatrix * vec4( position, 1.0f);
  tePosition = position;
  teNormal   = normal;
  teTexCoord = texCoord; // texCoord * diffuseScale
  //output.layerdef = layerdef;
  //output.depthmap_scaler = float4( 1.0, 1.0, detail_height, caustics);
  
}


float computeTessFactor(float d)
{
  float coef = (20.0f) / (d);
  return mix( uStaticTessFactor, coef*uDynamicTessFactor, uUseDynamicLOD);
}


float computeWaterCaustics(vec3 worldpos)
{
  float caustics = 0.0f;
  
  vec3 eyeToVert = worldpos - uEyePos;
  vec3 nVertToLight = normalize(uLightPosition - worldpos);
  
  // virtually move the vertex closer to -or away from- the light source depending
  // on its distance from the surface in order to simulate refraction.
  worldpos.xz += (-worldpos.y) * nVertToLight.xz;
  
  // XXX I guess it helps display caustics above surface (the 0.5f stuff)
  float waterDepth = 0.5f - worldpos.y;
  vec3 vertToWaterLight = waterDepth * nVertToLight;
  
  
  // XXX some mystic values
  const float k = 0.15f;
  const float m = 0.20f;
  
  /*
    OPTIMIZATION !
    
    vec2 base_uv = (eyeToVert.xz - 200.f) / 400.f
    float base_k = m*k / 400.f;
    //
    vec2 uv = base_uv + base_k*vec2(i,j);
  */
  
  // XXX OK, I don't get it
  // [NOT MY COMMENT] tracing approximately refracted rays back to light [NOT MY COMMENT]
  for (float i=-3.0f; i<=3.0f; i+=1.0f)
  {
    for (float j=-3.0f; j<=3.0f; j+=1.0f)
    {
      vec2 uv = (eyeToVert.xz + m*k*waterDepth*vec2(i, j) - 200.0f) / 400.0f;
      vec3 normal = textureLod( uWaterNormalmapTex, uv, 0).rgb;
      normal = 2.0f * normal - 1.0f;
      
      vec3 refracted = m * (vertToWaterLight + k*vec3(i, 0.0f, j)) - 0.5f*vec3( normal.x, 0.0f, normal.z);
      refracted = normalize(refracted);
      
      float dp = max( 0, dot( refracted, nVertToLight));
      caustics += 0.05f * pow( dp, 500.0f);
    }
  }
    
  // Fading out with distance to the vertex
  //float halfFadeDistance = 200.0f;
  //float distanceToEye = length(eyeToVert);
  //caustics *= halfFadeDistance / (halfFadeDistance + distanceToEye);

  // Fading out with distance from the water surface (under-water)
  // & disable caustics above water level.
  //caustics *= min(1, max(0,-position.y));
  
  return caustics;
}


--

//------------------------------------------------------------------------------


-- Fragment

in vec3 tePosition;
in vec3 teNormal;
in vec2 teTexCoord;

out vec4 fragColor;

uniform vec3 uEyePos;
uniform vec3 uEyeDir;
uniform vec3 uLightPosition = vec3( 217.0f, 100.0f, 217.0f);

uniform sampler2D uSandDiffuseTex;
uniform sampler2D uRockDiffuseTex;
uniform sampler2D uGrassDiffuseTex;
uniform sampler2D uSlopeDiffuseTex;

uniform sampler2D uNormalmap0Tex;
uniform sampler2D uNormalmap1Tex;
uniform sampler2D uNormalmap2Tex;


void main()
{  
  
  /****/
  /* Basic Texture splatting*/
  vec2 scaledUV = 40.0f * teTexCoord;
  vec4 sandColor = texture( uSandDiffuseTex, scaledUV);
  vec4 rockColor = texture( uRockDiffuseTex, scaledUV);
  vec4 grassColor = texture( uGrassDiffuseTex, scaledUV);
  vec4 slopeColor = texture( uSlopeDiffuseTex, 0.75f*scaledUV);  
  
  // f is in ~[ 0-eps; 1+eps]  :c
  float f = tePosition.y/30.0f;//0.5f + tePosition.y / 60.0f;
  vec4 color;
    
  
  const float SANDf  = 0.0f;
  const float GRASSf = 0.60f;
  const float SLOPEf = 0.95f;
  const float ROCKf  = 1.5f;
  
  const float midSG = 0.5f*(SANDf+GRASSf);
  const float midGS = 0.5f*(GRASSf+SLOPEf);
  const float midSR = 0.5f*(SLOPEf+ROCKf);
  
        if (f < SANDf) color = sandColor;
  else if (f < midSG)  color = mix( sandColor, grassColor, smoothstep( SANDf, midSG, f));
  else if (f < GRASSf) color = grassColor;
  else if (f < midGS)  color = mix( grassColor, slopeColor, smoothstep( GRASSf, midGS, f));
  else if (f < SLOPEf) color = slopeColor;
  else if (f < midSR)  color = mix( slopeColor, rockColor, smoothstep( SLOPEf, midSR, f));
  else color = rockColor;
  /*****/
  
  
  vec3 bumpNormal_rock = 2.0f*texture( uNormalmap0Tex, 50*teTexCoord).rgb-1.0f;
  vec3 bumpNormal_sand = 2.0f*texture( uNormalmap2Tex, 50*teTexCoord).rgb-1.0f;
  vec3 bumpNormal = mix( bumpNormal_sand, bumpNormal_rock, smoothstep(.3f, .7f, f) );
  bumpNormal = normalize( bumpNormal );

  // Let's create the TBN rotation matrix !    
  vec3 tangent  = normalize( cross( vec3( 1.0f, 0.0f, 0.0f), teNormal) );
  vec3 binormal = normalize( cross( tangent, teNormal) );
  mat3x3 NBT    = mat3x3( tangent, binormal, teNormal);

  vec3 normal = NBT * bumpNormal.rgb;
  
  vec3 lightDir = normalize( vec3( 0.25f, 1.0f, 0.5f));
  float diffuseFactor = dot( normal, lightDir);
  diffuseFactor = max( 0.0f, diffuseFactor);
  
  fragColor.rgb = diffuseFactor*color.rgb;
    
/*
    float4 color;
    float3 pixel_to_light_vector = normalize(g_LightPosition-input.positionWS);
    float3 microbump_normal; 

    float3x3 normal_rotation_matrix;

    // culling halfspace if needed
    clip(g_HalfSpaceCullSign*(input.positionWS.y-g_HalfSpaceCullPosition));
    
    // fetching default microbump normal
    microbump_normal = normalize(2*g_SandMicroBumpTexture.Sample(SamplerAnisotropicWrap,input.texcoord).rbg - float3 (1.0,1.0,1.0));
    microbump_normal = normalize(lerp(microbump_normal,2*g_RockMicroBumpTexture.Sample(SamplerAnisotropicWrap,input.texcoord).rbg - float3 (1.0,1.0,1.0),input.layerdef.w));

    //calculating base normal rotation matrix
    normal_rotation_matrix[1]=input.normal;
    normal_rotation_matrix[2]=normalize(cross(float3(-1.0,0.0,0.0),normal_rotation_matrix[1]));
    normal_rotation_matrix[0]=normalize(cross(normal_rotation_matrix[2],normal_rotation_matrix[1]));
    microbump_normal=mul(microbump_normal,normal_rotation_matrix);

    // getting diffuse color
    color=g_SlopeDiffuseTexture.Sample(SamplerAnisotropicWrap,input.texcoord);
    color=lerp(color,g_SandDiffuseTexture.Sample(SamplerAnisotropicWrap,input.texcoord),input.layerdef.g*input.layerdef.g);
    color=lerp(color,g_RockDiffuseTexture.Sample(SamplerAnisotropicWrap,input.texcoord),input.layerdef.w*input.layerdef.w);
    color=lerp(color,g_GrassDiffuseTexture.Sample(SamplerAnisotropicWrap,input.texcoord),input.layerdef.b);

    // adding per-vertex lighting defined by displacement of vertex 
    color*=0.5+0.5*min(1.0,max(0.0,input.depthmap_scaler.b/3.0f+0.5f));

    // calculating pixel position in light view space
    float4 positionLS = mul(float4(input.positionWS,1),g_LightModelViewProjectionMatrix);
    positionLS.xyz/=positionLS.w;
    positionLS.x=(positionLS.x+1)*0.5;
    positionLS.y=(1-positionLS.y)*0.5;


    // fetching shadowmap and shading
    float dsf=0.75f/4096.0f;
    float shadow_factor=0.2*g_DepthTexture.SampleCmp(SamplerDepthAnisotropic,positionLS.xy,positionLS.z* 0.995f).r;
    shadow_factor+=0.2*g_DepthTexture.SampleCmp(SamplerDepthAnisotropic,positionLS.xy+float2(dsf,dsf),positionLS.z* 0.995f).r;
    shadow_factor+=0.2*g_DepthTexture.SampleCmp(SamplerDepthAnisotropic,positionLS.xy+float2(-dsf,dsf),positionLS.z* 0.995f).r;
    shadow_factor+=0.2*g_DepthTexture.SampleCmp(SamplerDepthAnisotropic,positionLS.xy+float2(dsf,-dsf),positionLS.z* 0.995f).r;
    shadow_factor+=0.2*g_DepthTexture.SampleCmp(SamplerDepthAnisotropic,positionLS.xy+float2(-dsf,-dsf),positionLS.z* 0.995f).r;
    color.rgb*=max(0,dot(pixel_to_light_vector,microbump_normal))*shadow_factor+0.2;


    // adding light from the sky
    color.rgb+=(0.0+0.2*max(0,(dot(float3(0,1,0),microbump_normal))))*float3(0.2,0.2,0.3);

    // making all a bit brighter, simultaneously pretending the wet surface is darker than normal;
    color.rgb*=0.5+0.8*max(0,min(1,input.positionWS.y*0.5+0.5));


    // applying refraction caustics
    color.rgb*=(1.0+max(0,0.4+0.6*dot(pixel_to_light_vector,microbump_normal))*input.depthmap_scaler.a*(0.4+0.6*shadow_factor));

    // applying fog
    float3 pixel_to_eye_vector = normalize(g_CameraPosition-input.positionWS);
    color.rgb=lerp(CalculateFogColor(pixel_to_light_vector,pixel_to_eye_vector).rgb,color.rgb,min(1,exp(-length(g_CameraPosition-input.positionWS)*g_FogDensity)));
    color.a=length(g_CameraPosition-input.positionWS);
    return color;
*/
}

